﻿using System;
using System.Collections.Generic;
using System.Linq;
using AgentListener.Model.Database;
using Common.Configuration;
using Common.Annotations;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using NLog;

namespace AgentListener.Mongo
{
    //todo: добавить проверку при вызовах к базе - она может быть недоступна!

    internal class MongoProvider : IMongoProvider
    {
        private readonly IMongoConfig _config;
        private const string DumpCollectionName = "mdata";
        private const string AgentCollectionName = "agents";
        private const string AuthCollectionName = "auth";

        private readonly MongoServer _server;

        [NotNull]
        private readonly MongoConnectionStringBuilder _connection;

        [NotNull]
        private readonly MongoDatabase _db;

        private static readonly Logger NLog = LogManager.GetCurrentClassLogger();

        public MongoProvider([NotNull] IMongoConfig config)
        {
            if (config == null) throw new ArgumentNullException("config");
            NLog.Trace("Creating MongoProvider");
            DateTimeSerializationOptions.Defaults = DateTimeSerializationOptions.LocalInstance;
            _connection = new MongoConnectionStringBuilder();
            _config = config;

            NLog.Trace("Loading MongoProvider config");
            LoadConfig(_connection);

            NLog.Trace("Creating connection");
            _server = MongoServer.Create(_connection.ToServerSettings());

            NLog.Trace("Creating DB: {0}", _config.DatabaseName);

            if (!string.IsNullOrWhiteSpace(_config.Login))
            {
                var credentials = new MongoCredentials(_config.Login, _config.Password);
                _db = _server.GetDatabase(_config.DatabaseName, credentials);
            }
            else
                _db = _server.GetDatabase(_config.DatabaseName);
        }

        private void LoadConfig(MongoConnectionStringBuilder connection)
        {
            connection.ConnectionMode = _config.IsReplica
                                             ? ConnectionMode.ReplicaSet
                                             : ConnectionMode.Direct;

            NLog.Trace("ConnectionMode={0}", connection.ConnectionMode);
            NLog.Trace("Loading server list");

            connection.Servers = GetServers(_config);

            NLog.Trace("Loaded servers: {0}", connection.Servers.Count());
            NLog.Trace(connection.Servers.Aggregate("", (s, addr) => s += addr.ToString() + "; "));
        }

        private IEnumerable<MongoServerAddress> GetServers(IMongoConfig config)
        {
            return config.Hosts.Select(s =>
                {
                    var ubi = new UriBuilder(string.Format("mongo://{0}", s));
                    if (ubi.Port == -1)
                        ubi.Port = config.DefaultPort;

                    return new MongoServerAddress(ubi.Host, ubi.Port);
                }).ToArray();
        }

        private MongoCollection<T> GetCollection<T>([NotNull] string name)
        {
            if (name == null) throw new ArgumentNullException("name");

            try { return _db.GetCollection<T>(name); }
            catch (MongoConnectionException exc) { throw new MongoProviderException(exc); }
        }

        /// <summary>
        /// Получает данные об агенте из базы (может содержать большие объемы данных)
        /// </summary>
        public Agent GetAgent([NotNull] string agentId)
        {
            if (string.IsNullOrEmpty(agentId)) throw new ArgumentNullException("agentId");

            var id = ObjectId.Parse(agentId);

            return Agents.AsQueryable().SingleOrDefault(a => a.Id == id);
        }

        private MongoCollection<Agent> Agents { get { return GetCollection<Agent>(AgentCollectionName); } }
        private MongoCollection<Auth> Auths { get { return GetCollection<Auth>(AuthCollectionName); } }
        private MongoCollection<Dump> Dumps { get { return GetCollection<Dump>(DumpCollectionName); } }

        /// <summary>
        /// Сохраняет данные об агенте
        /// </summary>
        public void SaveAgent([NotNull] Agent agent)
        {
            if (agent == null) throw new ArgumentNullException("agent");

            NLog.Trace("Saving agent {0} {1}", agent.SysId, agent.Name);
            Agents.Save(agent);
        }

        public void SaveDump([NotNull] Dump dump)
        {
            if (dump == null) throw new ArgumentNullException("dump");

            Dumps.Save(dump);
        }

        public bool AuthExists(string agentId)
        {
            NLog.Trace("Try get hash by agentId={0}", agentId);
            var hashExists = Auths.AsQueryable().Any(a => a.AgentId == agentId);
            NLog.Trace("Hash exists: {0}", hashExists);
            return hashExists;
        }

        public void CreateAuth(string sysId, string agentId)
        {
            NLog.Trace("Creating auth: agentId={0}; agentId={1}", sysId, agentId);
            Auths.Insert(new Auth { SysId = sysId, AgentId = agentId });
        }
    }
}